package grex.genes;

import grex.Environment;
import grex.Options;
import grex.Prediction;
import grex.PredictionContainer;

import java.io.Serializable;
import java.util.Hashtable;

public abstract class Gene implements Cloneable, Serializable {

    public static final int TRAIN_MODE = 1, TEST_MODE = 2, OPTIMIZE_MODE = 3, CLEAN_MODE = 4;
    private int geneType;
    private int nrOfCildren;
    private Gene[] children;
    private String name;
    int level; //Level in the tree
    public Options ops;
    public Environment environment;
    public Gene parent;
    boolean modified = true;
    private PredictionContainer geneData;
    private String fold = "";

    abstract public void execute(Gene parent, PredictionContainer data, int mode) throws GeneException;

    abstract public Object clone();

    abstract public String toString();

    public Gene(int nrOfChildren) {
        initChildrens(nrOfChildren);
    }

    public void setOptions(Options ops) {
        this.ops = ops;
    }

    public void setParent(Gene parent) {
        this.parent = parent;
    }

    public Gene getParent() {
        return parent;
    }

    public void initChildrens(int nrOfChildren) {
        this.nrOfCildren = nrOfChildren;
        if (nrOfChildren > 0) {
            children = new Gene[nrOfChildren];
        }
    }

    public void setNodLevel(int level) {
        this.level = level;
        if (children != null) {
            for (int i = 0; i < children.length; i++) {
                children[i].setNodLevel(level + 1);
            }
        }
    }

    public int getNodeLevel() {
        return level;
    }

    public void cloneChildren(Gene copy) {
        Gene[] copyChildren = copy.getChildren();
        if (children != null) {
            for (int i = 0; i < children.length; i++) {
                copyChildren[i] = (Gene) children[i].clone();
                copyChildren[i].parent = copy;
            }
        }
    }

    public int getType() {
        return geneType;
    }

    public String getGeneName() {
        if (name == null) {
            String tmp = this.getClass().toString();
            name = tmp.substring(tmp.lastIndexOf('.') + 1);
        }
        return name;
    }

    public void setType(int geneType) {
        this.geneType = geneType;
    }

    public int getNrofChildren() {
        return nrOfCildren;
    }

    public Gene[] getChildren() {
        return children;
    }

    public void setChildren(Gene[] children) {
        this.children = children;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public boolean isModified() {
        return modified;
    }
    
    public void notifyParentsOfModification() {
        if (parent != null) {
           // if(!parent.modified){//optimzation to not notify  parents that is already set as modified. Mostly important for lokal regression mutatoin
                parent.modified = true;
                parent.notifyParentsOfModification();
           // }
        }
    }

    /*this method is needed for the prediction reuse.
     * If a condition or a terminal is chosen for crossover the if and all its children also
     * needs to be informed
     */
    public void notifyChildrenOfModification() {
        if (parent == null || this instanceof If) { // Either we are at the root node or its a if so just notify all the children
            setModified(true);
        }else{
            parent.notifyChildrenOfModification();//This is not an if so notify the parent instead
        }
        /*} else if (parent instanceof If) {
            parent.setModified(true); //Its a if notify all children of change
        } else if (parent.parent != null && parent.parent instanceof If) { //Its a terminal in a condition notify the parent.parent IF of modification
            parent.parent.setModified(true);
        } else {
            setModified(true);
        }*/

    }

    public void setModified(boolean modified) {
        this.modified = modified;
        if (children != null) {
            for (int i = 0; i < children.length; i++) {
                children[i].setModified(modified);
            }
        }
    }

    /**
     * @return the fold
     */
    public String getFold() {
        return fold;
    }

    /**
     * @param fold the fold to set
     */
    public void setFold(String fold) {
        this.fold = "" + fold;
    }
}
